/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package specifications;

import java.beans.IntrospectionException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.regex.Pattern;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.composer.Composer;
import org.yaml.snakeyaml.constructor.AbstractConstruct;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.events.*;
import org.yaml.snakeyaml.nodes.*;
import org.yaml.snakeyaml.parser.Parser;
import org.yaml.snakeyaml.parser.ParserImpl;
import org.yaml.snakeyaml.reader.StreamReader;
import org.yaml.snakeyaml.representer.Represent;
import org.yaml.snakeyaml.representer.Representer;
import org.yaml.snakeyaml.resolver.Resolver;
import rubric.ScoredDirective;

/**
 *
 * @author Jason
 */
public class Storage {

    private LinkingConstructor constructor = new LinkingConstructor();
    private LinkingRepresentor representor = new LinkingRepresentor();
    private DumperOptions dumper = new DumperOptions();
    public Yaml yaml = new Yaml(constructor, representor, dumper);

    public Storage() {
        yaml.addImplicitResolver(new Tag("!Link"), Pattern.compile("->.+"), "->");
        dumper.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
    }

    public <T> T load(File loadfile) throws FileNotFoundException {
        FileReader reader = new FileReader(loadfile);
        constructor.setRoot(loadfile.getParent());
        return (T) yaml.load(reader);
    }

    private class LinkingConstructor extends Constructor {

        private String root;

        public LinkingConstructor() {
            this.yamlConstructors.put(new Tag("!Link"), new ConstructBadLink());
            this.addTypeDescription(new TypeDescription(AdditionalTasks.class, AdditionalTasks.TAG));
            this.addTypeDescription(new TypeDescription(Audience.class, Audience.TAG));
            this.addTypeDescription(new TypeDescription(Complexity.class, Complexity.TAG));
            this.addTypeDescription(new TypeDescription(Contact.class, Contact.TAG));
            this.addTypeDescription(new TypeDescription(ContentCorrespondence.class, ContentCorrespondence.TAG));
            this.addTypeDescription(new TypeDescription(Count.class, Count.TAG));
            this.addTypeDescription(new TypeDescription(Directive.class, Directive.TAG));
            this.addTypeDescription(new TypeDescription(Expectations.class, Expectations.TAG));
            this.addTypeDescription(new TypeDescription(FileFormat.class, FileFormat.TAG));
            this.addTypeDescription(new TypeDescription(Layout.class, Layout.TAG));
            this.addTypeDescription(new TypeDescription(Origin.class, Origin.TAG));
            this.addTypeDescription(new TypeDescription(Permissions.class, Permissions.TAG));
            this.addTypeDescription(new TypeDescription(ProviderDirective.class, ProviderDirective.TAG));
            this.addTypeDescription(new TypeDescription(Purpose.class, Purpose.TAG));
            this.addTypeDescription(new TypeDescription(ReferenceMaterials.class, ReferenceMaterials.TAG));
            this.addTypeDescription(new TypeDescription(Register.class, Register.TAG));
            this.addTypeDescription(new TypeDescription(SpecializedLanguage.class, SpecializedLanguage.TAG));
            this.addTypeDescription(new TypeDescription(Specifications.class, Specifications.TAG));
            this.addTypeDescription(new TypeDescription(Style.class, Style.TAG));
            this.addTypeDescription(new TypeDescription(Submissions.class, Submissions.TAG));
            this.addTypeDescription(new TypeDescription(TargetLanguage.class, TargetLanguage.TAG));
            this.addTypeDescription(new TypeDescription(Technology.class, Technology.TAG));
            this.addTypeDescription(new TypeDescription(TextualCharacteristics.class, TextualCharacteristics.TAG));
            this.addTypeDescription(new TypeDescription(TypicalTasks.class, TypicalTasks.TAG));
            this.addTypeDescription(new TypeDescription(Volume.class, Volume.TAG));
            this.addTypeDescription(new TypeDescription(WorkplaceRequirements.class, WorkplaceRequirements.TAG));
            this.addTypeDescription(new TypeDescription(ScoredDirective.class, ScoredDirective.TAG));
        }

        public void setRoot(String path) {
            root = path;
        }

        @Override
        public void setComposer(Composer composer) {
            this.composer = new LinkingComposer(composer.getParser(), composer.getResolver());
            ((LinkingComposer) this.composer).setRoot(root);
        }

        private class ConstructBadLink extends AbstractConstruct {

            @Override
            public Object construct(Node node) {
                return new BadLink((String) constructScalar((ScalarNode) node));
            }
        }
    }

    private class LinkingRepresentor extends Representer {

        public LinkingRepresentor() {
            //Use RepresentModel for all model classes
            this.addClassTag(BadLink.class, new Tag("!Link"));
            this.addClassTag(AdditionalTasks.class, AdditionalTasks.TAG);
            this.addClassTag(Audience.class, Audience.TAG);
            this.addClassTag(Complexity.class, Complexity.TAG);
            this.addClassTag(Contact.class, Contact.TAG);
            this.addClassTag(ContentCorrespondence.class, ContentCorrespondence.TAG);
            this.addClassTag(Count.class, Count.TAG);
            this.addClassTag(Directive.class, Directive.TAG);
            this.addClassTag(Expectations.class, Expectations.TAG);
            this.addClassTag(FileFormat.class, FileFormat.TAG);
            this.addClassTag(Layout.class, Layout.TAG);
            this.addClassTag(Origin.class, Origin.TAG);
            this.addClassTag(Permissions.class, Permissions.TAG);
            this.addClassTag(ProviderDirective.class, ProviderDirective.TAG);
            this.addClassTag(Purpose.class, Purpose.TAG);
            this.addClassTag(ReferenceMaterials.class, ReferenceMaterials.TAG);
            this.addClassTag(Register.class, Register.TAG);
            this.addClassTag(SpecializedLanguage.class, SpecializedLanguage.TAG);
            this.addClassTag(Specifications.class, Specifications.TAG);
            this.addClassTag(Style.class, Style.TAG);
            this.addClassTag(Submissions.class, Submissions.TAG);
            this.addClassTag(TargetLanguage.class, TargetLanguage.TAG);
            this.addClassTag(Technology.class, Technology.TAG);
            this.addClassTag(TextualCharacteristics.class, TextualCharacteristics.TAG);
            this.addClassTag(TypicalTasks.class, TypicalTasks.TAG);
            this.addClassTag(Volume.class, Volume.TAG);
            this.addClassTag(WorkplaceRequirements.class, WorkplaceRequirements.TAG);
            this.addClassTag(ScoredDirective.class, ScoredDirective.TAG);
        }

        private class RepresentModel implements Represent {

            @Override
            public Node representData(Object data) {
                Model model = (Model) data;
                if (model.getLocation() != null) {
                    String value = "->" + model.getLocation();
                    return representScalar(new Tag("!Link"), value);
                } else {
                    try {
                        return representJavaBean(getProperties(data.getClass()), data);
                    } catch (IntrospectionException e) {
                        throw new YAMLException(e);
                    }
                }
            }
        }
    }

    private class LinkingComposer extends Composer {

        private String root;
        private String reference;

        public LinkingComposer(Parser parser, Resolver resolver) {
            super(parser, resolver);
        }

        public void setReference(String anchor) {
            reference = anchor;
        }

        public void setRoot(String path) {
            root = path;
        }

        @Override
        protected Node composeDocument() {
            // Drop the DOCUMENT-START event.
            getParser().getEvent();
            // Compose the root node.
            Node node = composeNode(null);
            // Drop the DOCUMENT-END event.
            getParser().getEvent();
            if (reference != null) {
                return this.getAnchors().get(reference);
            }
            this.getAnchors().clear();
            getRecursiveNodes().clear();
            return node;
        }

        @Override
        protected Node composeScalarNode(String anchor) {
            ScalarEvent ev = (ScalarEvent) getParser().getEvent();
            String tag = ev.getTag();
            boolean resolved = false;
            Tag nodeTag;
            if (tag == null || tag.equals("!")) {
                nodeTag = getResolver().resolve(NodeId.scalar, ev.getValue(), ev.getImplicit().canOmitTagInPlainScalar());
                resolved = true;
            } else {
                nodeTag = new Tag(tag);
            }
            Node node = new ScalarNode(nodeTag, resolved, ev.getValue(), ev.getStartMark(),
                    ev.getEndMark(), ev.getStyle());

            if (nodeTag.equals(new Tag("!Link"))) {
                String link = ((ScalarNode) node).getValue().replaceFirst("->", "");
                String linkFile;
                String linkAnchor;
                int pos = link.indexOf("@");
                if (pos != -1) {
                    linkFile = link.substring(0, pos);
                    linkAnchor = link.substring(pos + 1);
                } else {
                    linkFile = link;
                    linkAnchor = null;
                }
//                System.out.println(root);
//                System.out.println(linkFile);
//                System.out.println(root+"/"+linkFile);
//                System.out.println(linkAnchor);
//                System.out.println(root+"/"+linkFile+"@"+linkAnchor);
                File file = new File(root + "/" + linkFile);
                FileReader reader;
                if (file.exists()) {
                    try {
                        reader = new FileReader(file);
                    } catch (FileNotFoundException ex) {
                        System.err.println(ex);
                        return node; //shortcircuit on impossible error
                    }
                } else {
                    try {
                        reader = new FileReader(linkFile);
                    } catch (FileNotFoundException ex) {
                        System.err.println(ex);
                        return node; //shortcircuiting
                    }
                }

                LinkingComposer comp = new LinkingComposer(new ParserImpl(new StreamReader(reader)), this.getResolver());
                comp.setRoot(file.getParent());
                comp.setReference(linkAnchor);
                node = comp.getSingleNode(); //Currently only single documents are supported as links
            }
            if (anchor != null) {
                getAnchors().put(anchor, node);
            }
            return node;
        }
    }

    private class BadLink {

        private String reference;

        public BadLink() {
        }

        public BadLink(String ref) {
            reference = ref;
        }

        /**
         * @return the reference
         */
        public String getReference() {
            return reference;
        }

        /**
         * @param reference the reference to set
         */
        public void setReference(String reference) {
            this.reference = reference;
        }

        @Override
        public String toString() {
            return reference;
        }
    }
}
